<?php
// +---------------------------------------------------------------------+
// | OneBase    | [ WE CAN DO IT JUST THINK ]                            |
// +---------------------------------------------------------------------+
// | Licensed   | http://www.apache.org/licenses/LICENSE-2.0 )           |
// +---------------------------------------------------------------------+
// | Author     | 王勇程 <laughskydragon@qq.com>                         |
// +---------------------------------------------------------------------+
// | Repository | https://gitee.com/Bigotry/OneBase                      |
// +---------------------------------------------------------------------+

namespace app\common\model;

use think\Model;
use think\facade\Db;

/**
 * 模型基类
 */
class ModelBase extends Model
{

    /**
     * 连接查询
     */
    protected $join = [];

    // 显式声明主表别名，避免动态属性导致查询时被当作字段或被混入 where
    protected $alias_name = '';
    
    // 强制自动时间戳写入为整型，避免写入 'Y-m-d H:i:s.u' 导致 int 列截断
    protected $autoWriteTimestamp = 'int';

    /**
     * 状态获取器
     */
    public function getStatusTextAttr($value = null, $data = [])
    {
        
        $status = [DATA_DELETE => '删除', DATA_DISABLE => "<span class='badge bg-red'>禁用</span>", DATA_NORMAL => "<span class='badge bg-green'>启用</span>"];
        
        // 兼容：优先从 $data 拿，回退 getData()，最后给出一个默认值（禁用）
        $field = DATA_STATUS_NAME;
        $raw = array_key_exists($field, $data)
            ? $data[$field]
            : (is_array($this->getData()) && array_key_exists($field, $this->getData()) ? $this->getData()[$field] : DATA_DISABLE);
        
        return $status[$raw] ?? $status[DATA_DISABLE];
    }
    
    /**
     * 设置数据
     */
    final protected function setInfo($data = [], $where = [], $select_model = false)
    {
        
        $this->updateCache($this);
        
        $pk = $this->getPk();
        
        if (empty($data[$pk])) {
            
            if (empty($where)) {
                
                $select_model?:empty($data[TIME_CT_NAME]) && $data[TIME_CT_NAME] = time();
                
                // 这里修复：getTable() 已含前缀，使用 table() 避免二次前缀
                return $select_model ? $this->data($data)->save() : Db::table($this->getTable())->insertGetId($data);
            }
                
            return $this->updateInfo($where, $data);
            
        } else {
            
            is_object($data) && $data = $data->toArray();
            
            !empty($data[TIME_CT_NAME]) && is_string($data[TIME_CT_NAME]) && $data[TIME_CT_NAME] = strtotime($data[TIME_CT_NAME]);
            
            $default_where[$pk] = $data[$pk];
            
            return $this->updateInfo(array_merge($default_where, $where), $data);
        }
    }
    
    /**
     * 更新数据
     */
    final protected function updateInfo($where = [], $data = [])
    {
        
        $data[TIME_UT_NAME] = TIME_NOW;
        
        $this->updateCache($this);
        
        // 兼容 TP8：不再使用 allowField(true) 和 save($data, $where)
        // 使用 exists(true) + setUpdateWhere($where) + save($data)
        if (!empty($where)) {
            $this->exists(true);
            // 修复：更新路径同样需要归一化 where，避免 ['neq', x] 被误判为 IN
            $this->setUpdateWhere($this->normalizeWhere($where));
        }

        return $this->save($data);
    }
    
    /**
     * 统计数据
     */
    final protected function stat($where = [], $stat_type = 'count', $field = 'id')
    {
        
        return $this->where($this->normalizeWhere($where))->$stat_type($field);
    }
    
    /**
     * 设置数据列表
     */
    final protected function setList($data_list = [], $replace = false)
    {
        
        $return_data = $this->saveAll($data_list, $replace);
        
        $this->updateCache($this);
        
        return $return_data;
    }
    
    /**
     * 设置某个字段值
     */
    final protected function setFieldValue($where = [], $field = '', $value = '')
    {
        
        $this->updateCache($this);
        
        return $this->updateInfo($where, [$field => $value]);
    }
    
    /**
     * 删除数据
     */
    final protected function deleteInfo($where = [], $is_true = false)
    {
        
        $this->updateCache($this);
        
        if ($is_true) {
            
            // 修复：物理删除时同样归一化 where，避免 ['neq', x] 被误判为 IN
            $return_data = $this->where($this->normalizeWhere($where))->delete();
            
        } else {
            
            $return_data = $this->setFieldValue($where, DATA_STATUS_NAME, DATA_DELETE);
        }
        
        return $return_data;
    }
    
    /**
     * 获取某个列的数组
     */
    final protected function getColumn($where = [], $field = '', $key = '')
    {
        
        // 这里修复：getTable() 已含前缀，使用 table() 避免二次前缀
        return Db::table($this->getTable())->where($this->normalizeWhere($where))->column($field, $key);
    }
    
    /**
     * 获取某个字段的值
     * 兼容双路调用：
     * 1) TP8 内核 Attribute::getValue($name, $value, $relation)
     * 2) OneBase 业务 getValue($where, $field, $default, $force)
     */
    final protected function getValue($where = [], $field = '', $default = null, $force = false)
    {
        // 分发到 TP8 内核路径：当为3参调用且形态匹配“属性名 + 属性值 + 关系”
        if (func_num_args() === 3 && is_string($where) && (is_bool($default) || is_string($default))) {
            // $where => $name, $field => $value, $default => $relation
            return parent::getValue($where, $field, $default);
        }

        // 业务路径：字段参数校验，避免传入空字符串或非字符串导致 BaseQuery::value 类型错误
        if (!is_string($field) || $field === '') {
            return $default;
        }

        // 防御性剔除误混入的 alias_name 条件，避免生成 WHERE alias_name IS NULL
        if (is_array($where) && array_key_exists('alias_name', $where)) {
            unset($where['alias_name']);
        }
        
        // 这里修复：getTable() 已含前缀，使用 table() 避免二次前缀
        return Db::table($this->getTable())->where($this->normalizeWhere($where))->value($field, $default, $force);
    }
    
    /**
     * 指定主表别名（重载），避免被动态转发到 Query 导致别名丢失
     */
    public function alias($alias = '')
    {
        if (is_string($alias)) {
            $alias = trim($alias);
            if ($alias !== '') {
                $this->alias_name = $alias;
            }
        }
        // 保持链式调用
        return $this;
    }
    
    /**
     * 获取单条数据
     */
    final protected function getInfo($where = [], $field = true)
    {
        
        // 统一从 db() 开始创建查询对象，并应用别名
        $query = $this->db();
        if (is_string($this->alias_name) && $this->alias_name !== '') {
            $query = $query->alias($this->alias_name);
        }
    
        // 修复：按当前 ORM 逐条追加 join，兼容 [table,on] 与 [table,on,type] 两种格式
        if (!empty($this->join) && is_array($this->join)) {
            foreach ($this->join as $j) {
                if (!is_array($j) || empty($j[0]) || empty($j[1])) {
                    continue;
                }
                $table = $j[0];
                $on    = $j[1];
                $type  = isset($j[2]) && is_string($j[2]) ? $j[2] : 'INNER';
                // 只有当 $table 和 $on 都是字符串时才追加，避免 null 进入 ORM
                if (is_string($table) && is_string($on) && $on !== '') {
                    $query = $query->join($table, $on, $type);
                }
            }
        }
        
        $ob_auto_cache_key = $this->getCacheKey($this, $query, $where, $field);
        
        $is_update_cache = $this->checkCacheVersion($this, $query);
        
        // 修复：仅在缓存键不为空且无需更新缓存版本时才尝试读取缓存，避免向 cache 传入数组
        $cache_data = null;
        if (!empty($ob_auto_cache_key) && !$is_update_cache) {
            $cache_data = cache($ob_auto_cache_key);
            if (!empty($cache_data)) {
                return $cache_data;
            }
        }
        
        $info = $query->where($this->normalizeWhere($where))->field($field)->find();
    
        // 统一转换为数组，避免上层逻辑使用 is_array 判断误判为“不存在”
        if ($info instanceof \think\Model) {
            $info = $info->toArray();
        }
    
        $this->setCache($this, $info, $ob_auto_cache_key);
        
        $this->join = [];
        
        return $info;
    }
    
    /**
     * 获取列表数据
     * 若不需要分页 $paginate 设置为 false
     */
    final protected function getList($where = [], $field = true, $order = '', $paginate = 0)
    {
        
        list($query, $where_temp) = $this->getListQuery($this, $where);
        
        $ob_auto_cache_key = $this->getCacheKey($this, $query, $where_temp, $field, $order, $paginate);
        
        $is_update_cache = $this->checkCacheVersion($this, $query);
        
        // 修复：仅在缓存键不为空且无需更新缓存版本时才尝试读取缓存
        $cache_data = null;
        if (!empty($ob_auto_cache_key) && !$is_update_cache) {
            $cache_data = cache($ob_auto_cache_key);
            if (!empty($cache_data)) {
                return $cache_data;
            }
        }
                
        $query_temp = $query->where($where_temp)->order($order)->field($field);
        
        !empty($this->limit) && $query_temp->limit($this->limit);
        !empty($this->group) && $query_temp->group($this->group);
        
        if (false === $paginate) {
       
            $list = $query_temp->select();
            
        } else {
        
            $listRows = (int) input('list_rows', empty($paginate) ? (defined('DB_LIST_ROWS') ? DB_LIST_ROWS : 10) : $paginate);
            // 显式设置分页 path，兜底为 '/'
            $path = request()->baseUrl();
            if (!is_string($path) || $path === '') {
                $path = '/';
            }
        
            // 保持你项目当前的调用签名，第三个参数作为配置传入（兼容当前 Model::paginate 代理）
            $list = $query_temp->paginate(
                $listRows,
                false,
                [
                    'query' => request()->param(),
                    'path'  => $path,
                ]
            );
        }
        
        $this->join = []; $this->limit = []; $this->group = [];
        
        $this->setCache($this, $list, $ob_auto_cache_key);
        
        return $list;
    }
    
    /**
     * 获取列表查询query对象
     */
    final protected function getListQuery($obj = null, $where = [])
    {
        
        // 修复默认状态条件：改为三元条件，避免被 ORM 误判为 IN
        if (empty($obj->connection) && empty($obj->join)) {
            $hasStatus = false;
            if (is_array($where)) {
                if (array_key_exists(DATA_STATUS_NAME, $where)) {
                    $hasStatus = true;
                } else {
                    foreach ($where as $wk => $wv) {
                        if (is_int($wk) && is_array($wv) && isset($wv[0]) && $wv[0] === DATA_STATUS_NAME) {
                            $hasStatus = true; break;
                        }
                    }
                }
            }
            if (!$hasStatus) {
                $where[] = [DATA_STATUS_NAME, '<>', DATA_DELETE];
            }
        }
    
        // 统一从 db() 开始构建查询对象，先应用别名
        $query = $obj->db();
        // 直接读取已持久化的别名并应用
        $alias = is_string($obj->alias_name ?? null) ? $obj->alias_name : '';
        if ($alias !== '') {
            $query = $query->alias($alias);
        }
        
        if (empty($obj->join)) {
            // 无联表直接返回
        } else {
            // 修复：逐条 join，兼容 [table,on] 与 [table,on,type]
            if (is_array($obj->join)) {
                foreach ($obj->join as $j) {
                    if (!is_array($j) || empty($j[0]) || empty($j[1])) {
                        continue;
                    }
                    $table = $j[0];
                    $on    = $j[1];
                    $type  = isset($j[2]) && is_string($j[2]) ? $j[2] : 'INNER';
                    if (is_string($table) && is_string($on) && $on !== '') {
                        $query = $query->join($table, $on, $type);
                    }
                }
            }
        }
    
        // 在所有查询出口前统一归一化 where
        $where = $this->normalizeWhere($where);
        return [$query, $where];
    }
    
    /**
     * 原生查询
     */
    final protected function dbQuery($sql = '')
    {
        
        return Db::query($sql);
    }
    
    /**
     * 原生执行
     */
    final protected function dbExecute($sql = '')
    {
        
        return Db::execute($sql);
    }
    
    /**
     * 更新缓存
     */
    final protected function updateCache($obj = null)
    {
        
        config('is_auto_cache') && !isset($obj->is_update_cache_version) && update_cache_version($obj);
    }
    
    /**
     * 清除多表缓存
     */
    final protected function clearTablesCache($tables = [])
    {
            
        $ob_auto_cache_keys = cache('ob_auto_cache_keys');

        foreach ($ob_auto_cache_keys as $k => $v) {

            foreach ($tables as $vv) {

                $pos = strpos($v, $vv);

                if ($pos !== false) {

                    cache($v, null);

                    unset($ob_auto_cache_keys[$k]);
                }
            }
        }

        cache('ob_auto_cache_keys', array_values($ob_auto_cache_keys));
    }
    
    /**
     * 写入缓存
     */
    final protected function setCache($obj = null, $data = [], $ob_auto_cache_key = '')
    {
        
        if (config('is_auto_cache') && !isset($obj->no_auto_cache) && !empty($ob_auto_cache_key)) {
            
            !empty($data) && cache($ob_auto_cache_key, $data, config('auto_cache_time'));
        }
    }
    
    /**
     * 获取缓存
     */
    final protected function getCacheKey($obj = null, $query = null, $where = [], $field = true, $order = null, $paginate = null)
    {
        
        // 修复：在禁用自动缓存或设置了 no_auto_cache 时返回空字符串而不是数组，避免后续 cache() 调用类型错误
        if (!config('is_auto_cache') || isset($obj->no_auto_cache)) { return ''; }
        
        $where['field']     = $field;
        
        isset($order)       && $where['order'] = $order;
        isset($paginate)    && $where['paginate'] = $paginate;
        
        $ob_auto_cache      = cache('ob_auto_cache');
        
        $table_name = $query->getTable();
        
        unset($ob_auto_cache[$table_name]['data_version']);
      
        $table_arr = $this->getTableArray($obj);
        
        $temp_str = arr22str($obj->join);
        
        $temp_str = $table_name . json_encode($obj) . json_encode($where) . json_encode($ob_auto_cache[$table_name]);
        
        isset($order) && $temp_str .= json_encode(request()->param());
        
        $ck = arr2str($table_arr, '*') . '$' . md5($temp_str);
        
        $ob_auto_cache_keys = cache('ob_auto_cache_keys');
        
        if (!in_array($ck, $ob_auto_cache_keys)) {
            
            $ob_auto_cache_keys[] = $ck;
            
            cache('ob_auto_cache_keys', $ob_auto_cache_keys);
        }
        
        return $ck;
    }
    
    /**
     * 获取表数组
     */
    final protected function getTableArray($obj = null)
    {
        
        $temp_str = arr22str($obj->join);
        
        $preg = SYS_DS_PROS . SYS_DB_PREFIX . '[\s\S]*? /i';
        
        $res_data = [];
        
        preg_match_all($preg, $temp_str, $res_data);
        
        $table_arr = [];
        
        foreach ($res_data[DATA_DISABLE] as $v) {
            
            $table_arr[] = sr($v, ' ');
        }
        
        return $table_arr;
    }
    
    /**
     * 检查缓存版本
     */
    final protected function checkCacheVersion($obj = null, $query = null)
    {
        
        if (!config('is_auto_cache') || isset($obj->no_auto_cache)) { return true; }
        
        $table_name = $query->getTable();
        
        $table_arr = $this->getTableArray($obj);
        
        $table_arr[] = $table_name;
        
        $ob_auto_cache_data = cache('ob_auto_cache');
        
        $is_update = false;
        
        foreach ($table_arr as $v) {
            
            if ($ob_auto_cache_data[$v]['version'] != $ob_auto_cache_data[$v]['data_version']) {
                
                $is_update = true;
                
                $ob_auto_cache_data[$v]['data_version'] = $ob_auto_cache_data[$v]['version'];
            }
        }
        
        cache('ob_auto_cache', $ob_auto_cache_data);
        
        $is_update && $this->clearTablesCache($table_arr);
        
        return $is_update;
    }
    
    /**
     * 重写获取器 兼容 模型|逻辑|验证|服务 层实例获取
     */
    public function __get($name)
    {
        
        $layer = $this->getLayerPrefix($name);
        
        if (false === $layer) {
            
            return parent::__get($name);
        }
        
        $model = sr($name, $layer);
        
        return model($model, $layer);
    }
    
    /**
     * 获取层前缀
     */
    public function getLayerPrefix($name)
    {
        
        $layer = false;
        
        $layer_array = [LAYER_MODEL_NAME, LAYER_LOGIC_NAME, LAYER_VALIDATE_NAME, LAYER_SERVICE_NAME];
        
        foreach ($layer_array as $v)
        {
            if (str_prefix($name, $v)) {
                
                $layer = $v;
                
                break;
            }
        }
        
        return $layer;
    }
    
    /**
     * 读取时间字段值时的健壮性修复：
     * 数值型（int 或纯数字字符串）统一按时间戳处理，避免 DateTime($int) 构造异常
     */
    protected function getTimestampValue($value)
    {
        // 数值优先：只要是数值型，就走 setTimestamp 路径
        if (is_int($value) || (is_string($value) && ctype_digit($value))) {
            $format = $this->getDateFormat() ?: 'Y-m-d H:i:s';
            return $this->formatDateTime($format, $value, true);
        }

        // 保留 TP8 原始逻辑
        $type = $this->checkTimeFieldType($this->autoWriteTimestamp);

        if (is_string($type) && in_array(strtolower($type), ['datetime', 'date', 'timestamp'])) {
            return $this->formatDateTime($this->getDateFormat(), $value);
        }

        return $this->formatDateTime($this->getDateFormat(), $value, true);
    }

    /**
     * 归一化 where 条件（将 TP5 风格统一转换为 TP8 兼容的三元条件）
     * 支持：
     * - ['field' => ['neq', -1]]      => [['field','<>',-1]]
     * - ['field' => 1]                => [['field','=',1]]
     * - [['field','neq',-1], ...]     => 映射操作符后原样保留
     * - [['field',['neq',-1]], ...]   => 拆解为三元
     */
    protected function normalizeWhere($where)
    {
        if (!is_array($where) || empty($where)) { return $where; }

        $opMap = [
            'eq' => '=', 'neq' => '<>', 'gt' => '>', 'lt' => '<',
            'egt' => '>=', 'elt' => '<=', 'like' => 'like', 'not like' => 'not like',
            'in' => 'in', 'not in' => 'not in', 'between' => 'between', 'not between' => 'not between'
        ];

        $normalized = [];

        // 判断是否为列表数组
        $is_list_array = array_keys($where) === range(0, count($where) - 1);

        if ($is_list_array) {
            foreach ($where as $item) {
                if ($item instanceof \Closure) {
                    $normalized[] = $item;
                    continue;
                }
                if (is_array($item)) {
                    // 形如 ['field',['neq',-1]]
                    if (isset($item[0]) && isset($item[1]) && is_array($item[1]) && count($item[1]) === 2 && is_string($item[1][0])) {
                        $op = strtolower($item[1][0]);
                        $op = $opMap[$op] ?? $item[1][0];
                        $normalized[] = [$item[0], $op, $item[1][1]];
                        continue;
                    }
                    // 形如 ['field','neq',-1]
                    if (isset($item[0]) && isset($item[1]) && isset($item[2]) && is_string($item[1])) {
                        $op = strtolower($item[1]);
                        $op = $opMap[$op] ?? $item[1];
                        $normalized[] = [$item[0], $op, $item[2]];
                        continue;
                    }
                }
                // 其他情况原样压入，交由 ORM 处理
                $normalized[] = $item;
            }
            return $normalized;
        }

        // 关联数组：形如 ['status' => ['neq', -1]] 或 ['id' => 1]
        foreach ($where as $field => $cond) {
            if ($field === 'alias_name') { // 防御性剔除
                continue;
            }
            if ($cond instanceof \Closure) {
                $normalized[] = $cond;
                continue;
            }
            if (is_array($cond)) {
                // 优先识别标准三元：['field','op', value]（包括通过 $where[] 追加的三元）
                if (count($cond) === 3 && is_string($cond[1])) {
                    $op = strtolower($cond[1]);
                    $op = $opMap[$op] ?? $cond[1];
                    // 若三元自身提供了字段名（字符串），直接使用它；否则退回使用当前键
                    if (is_string($cond[0]) && $cond[0] !== '') {
                        $normalized[] = [$cond[0], $op, $cond[2]];
                    } else {
                        $normalized[] = [$field, $op, $cond[2]];
                    }
                    continue;
                }
                // 标准二元：['op', value]（键即为字段名）
                if (count($cond) === 2 && is_string($cond[0])) {
                    $op = strtolower($cond[0]);
                    $op = $opMap[$op] ?? $cond[0];
                    $normalized[] = [$field, $op, $cond[1]];
                    continue;
                }
                // 无法判定时，兜底等值（避免误判为 IN）
                $normalized[] = [$field, '=', reset($cond)];
            } else {
                // 标量等值
                $normalized[] = [$field, '=', $cond];
            }
        }

        return $normalized;
    }
    
    /**
     * 获取错误信息
     * 兼容 OneBase 逻辑层调用 $this->modelXxx->getError() 的需求
     * 
     * @author 王勇程 <laughskydragon@qq.com>
     * @return string
     */
    public function getError()
    {
        // 获取模型的错误信息，优先从异常中获取
        if (!empty($this->error)) {
            return $this->error;
        }
        
        // 如果没有错误信息，返回通用错误
        return '操作失败';
    }
}
